# -*- coding: utf-8 -*-
# @Time : 2024/11/6 23:51
from fastapi import FastAPI
import os
import time
import json
import random
import base64
import requests
from PIL import Image
from io import BytesIO
from loguru import logger
from Crypto.Cipher import AES
from Crypto.Hash import SHA512
from dotenv import load_dotenv

load_dotenv()

H5_SDK_VERSION = os.getenv("H5_SDK_VERSION", '3.5.64')
SALT = os.getenv("SALT",
                 "9ffa469fae9790fbd32e179f0d4086dd44fa974a0303d33c8e9fb83bc0deb4d4c37469905e263a88af7a61845a8c4a8a8af9fe207fc656c975c3a2693179ead5")
app = FastAPI()


def get_random_detail(fp):
    headers = {
        "authority": "fanqienovel.com",
        "accept": "application/json, text/javascript",
        "accept-language": "zh-CN,zh;q=0.9",
        "cache-control": "no-cache",
        "content-type": "application/x-www-form-urlencoded",
        "origin": "https://fanqienovel.com",
        "pragma": "no-cache",
        "referer": "https://fanqienovel.com/main/writer/login",
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "x-secsdk-csrf-token": "000100000001b7e57516eb615618fc1d1a2db17d175d1181e1a3993be1599744ebbdc47c613317a98c268e657d51",
        "x-tt-passport-csrf-token": ""
    }
    cookies = {
        "csrf_session_id": "0047aad17dcbf505e3d02d7b230a9115",
        "ttwid": "1%7CvVjB9g-K18vU0-N5tasWgX0cn-8qOa4WE0Xc1PxheJM%7C1705048031%7C2d7b96a54e369164b08ece6822fd7e364f240fc5dd4f12dbe83aece00f9dabca",
        "s_v_web_id": fp
    }
    url = "https://fanqienovel.com/passport/web/send_code/"
    params = {
        "aid": "2503",
        "account_sdk_source": "web",
        "sdk_version": "2.2.6-beta.2",
        "verifyFp": fp,
        "fp": fp
    }
    data = {
        "mix_mode": "1",
        "mobile": "2e3d3325343037373737303d313032",
        "type": "3731",
        "fixed_mix_mode": "1"
    }
    response = requests.post(url, headers=headers, cookies=cookies, params=params, data=data, timeout=5)
    data_dict = response.json()
    verify_center_decision_conf = json.loads(data_dict['data']['verify_center_decision_conf'])
    # logger.info(verify_center_decision_conf)
    fp = verify_center_decision_conf['fp']
    detail = verify_center_decision_conf['detail']
    return {
        'fp': fp,
        'detail': detail,
    }
def dumps(data_dict):
    """
    字典数据序列化
    :return:
    """
    import json
    return json.dumps(data_dict, separators=(',', ':'),ensure_ascii=False)


def captcha_encrypt(data: dict) -> str:
    """
    AES GCM
    """

    def aes_gcm_encrypt(key, nonce, plaintext):
        cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
        plaintext = cipher.encrypt_and_digest(plaintext)
        return plaintext

    # 加密字节
    v8 = json.dumps(data, separators=(',', ':')).encode()
    # sha521加密 + 加密字节
    v11 = SHA512.new(v8).digest() + v8
    # 32位的盐
    slat = ''.join(random.choices("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", k=32))
    v12 = SHA512.new(bytes.fromhex(SHA512.new(slat.encode()).hexdigest() + SALT)).hexdigest()
    ciphertext, mac = aes_gcm_encrypt(key=bytes.fromhex(v12[:64]), nonce=bytes.fromhex(v12[64: 88]), plaintext=v11)
    return base64.b64encode(bytes([116, 99, 6, 16, 0, 0]) + slat.encode() + ciphertext + mac).decode()


def captcha_decrypt(enc_data: str) -> str:
    def aes_gcm_decrypt(key, nonce, ciphertext, tag):
        cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
        plaintext = cipher.decrypt_and_verify(ciphertext, tag)
        return plaintext

    data_bytes = base64.b64decode(enc_data.encode())
    hedaers_bytes = data_bytes[:6]
    # 秘文标志b'tc\x06\x10\x00\x00'
    if hedaers_bytes != bytes([116, 99, 6, 16, 0, 0]):
        raise Exception("Invalid header")
    slat_bytes = data_bytes[6: 38]  # 32
    cipher_bytes = data_bytes[38: -16]  # 38到后16位

    magic_bytes = data_bytes[-16:]  # 后16位
    # SALT  两把盐{密文base64字节流 + 页面js固定} => key-iv
    key_iv = SHA512.new(bytes.fromhex(
        SHA512.new(slat_bytes).hexdigest() +
        SALT
    )).hexdigest()
    key = key_iv[:64]
    iv = key_iv[64: 88]
    return json.loads(aes_gcm_decrypt(bytes.fromhex(key), bytes.fromhex(iv), cipher_bytes, magic_bytes)[64:].decode())


def get_distance(target_bytes, background_bytes):
    """
    ddddocr 识别滑动距离
    """
    import ddddocr
    det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)  # , show_ad=False
    return int(det.slide_match(target_bytes, background_bytes)["target"][0] * 0.6159420289855072)


def save_file(file_path: str, bs: bytes):
    # "./repair.jpg", repair_bytes
    # 写入数据到文件中
    with open(file_path, mode='wb') as f:
        f.write(bs)
    # logger.info("{} 图片保存成功！".format(file_path))


def get_fp():
    e = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    t = len(e)
    milliseconds = int(round(time.time() * 1000))
    base36 = ''
    while milliseconds > 0:
        remainder = milliseconds % 36
        if remainder < 10:
            base36 = str(remainder) + base36
        else:
            base36 = chr(ord('a') + remainder - 10) + base36
        milliseconds = int(milliseconds / 36)
    r = base36
    o = [''] * 36
    o[8] = o[13] = o[18] = o[23] = '_'
    o[14] = '4'

    for i in range(36):
        if not o[i]:
            n = 0 or int(random.random() * t)
            if i == 19:
                n = 3 & n | 8
            o[i] = e[n]
    ret = "verify_" + r + "_" + ''.join(o)
    return ret


def get_tracks(distance, _y):
    """
    获取轨迹参数
    """
    import random
    tracks = list()
    y, v, t, current = 0, 0, 1, 0
    mid = distance * 3 / 4
    exceed = random.randint(40, 90)
    z = random.randint(30, 150)
    while current < (distance + exceed):
        if current < mid / 2:
            a = 2
        elif current < mid:
            a = 3
        else:
            a = -3
        a /= 2
        v0 = v
        s = v0 * t + 0.5 * a * (t * t)
        current += int(s)
        v = v0 + a * t
        y += random.randint(-3, 3)
        z = z + random.randint(5, 10)
        tracks.append([min(current, (distance + exceed)), y, z])
    while exceed > 0:
        exceed -= random.randint(0, 5)
        y += random.randint(-3, 3)
        z = z + random.randint(5, 9)
        tracks.append([min(current, (distance + exceed)), y, z])
    tr = []
    for i, x in enumerate(tracks):
        tr.append({
            'x': x[0],
            'y': _y,
            'relative_time': x[2]
        })
    return tr


def get_current_timestamp13():
    """
    获取13位数时间戳
    :return:
    """
    return "{}".format(int(time.time() * 1e3))


def repair_image(bg_resp_content: bytes) -> bytes:
    """
    图片还原
    """
    # 加载原始滑块验证码图片
    image_bytes_io = BytesIO(bg_resp_content)
    # 使用 Image.open() 打开类文件对象
    original_image = Image.open(image_bytes_io)
    # 计算每个小块的长度
    width, height = original_image.size
    block_width = width // 6
    # 分割图片为6个小块
    slices = []
    for i in range(6):
        left = i * block_width
        right = (i + 1) * block_width
        slices.append(original_image.crop((left, 0, right, height)))

    # 正确还原顺序
    slices[2], slices[4] = slices[4], slices[2]
    slices[3], slices[5] = slices[5], slices[3]
    slices[0], slices[2] = slices[2], slices[0]
    slices[1], slices[5] = slices[5], slices[1]
    slices[1], slices[2] = slices[2], slices[1]
    # 创建一个新的图片对象，将处理后的小块图片合并为一张图片
    new_image = Image.new('RGB', (width, height))
    for i in range(6):
        new_image.paste(slices[i], (i * block_width, 0))
    image_bytes_io = BytesIO()
    new_image.save(image_bytes_io, format='PNG')
    return image_bytes_io.getvalue()


def image_restore(byte_stream):
    """
    图片还原 保存图片
    """
    # 加载原始滑块验证码图片
    # original_image = Image.open(bg_path)

    original_image = Image.open(BytesIO(byte_stream))
    width, height = original_image.size
    block_width = width // 6
    slices = []
    for i in range(6):
        left = i * block_width
        right = (i + 1) * block_width
        slices.append(original_image.crop((left, 0, right, height)))
    # 正确还原顺序
    slices[2], slices[4] = slices[4], slices[2]
    slices[3], slices[5] = slices[5], slices[3]
    slices[0], slices[2] = slices[2], slices[0]
    slices[1], slices[5] = slices[5], slices[1]
    slices[1], slices[2] = slices[2], slices[1]
    # 创建一个新的图片对象，将处理后的小块图片合并为一张图片
    new_image = Image.new('RGB', (width, height))
    for i in range(6):
        new_image.paste(slices[i], (i * block_width, 0))

    # 创建一个 BytesIO 对象
    byte_stream = BytesIO()

    # 将 Image 对象保存到 BytesIO 对象
    new_image.save(byte_stream, format='JPEG')

    # 获取字节流
    byte_stream.seek(0)  # 将指针移动到开头
    repair_bytes = byte_stream.getvalue()
    return repair_bytes

    # repair_path = "./repair.jpg"
    # print('new_image', type(new_image))
    # new_image.save(repair_path)
    # return repair_path


class DouYinSlide():

    def __init__(self):

        self.headers =  {
    "accept": "*/*",
    "accept-language": "zh-CN,zh;q=0.9",
    "cache-control": "no-cache",
    "content-type": "text/plain;charset=UTF-8",
    "origin": "https://rmc.bytedance.com",
    "pragma": "no-cache",
    "priority": "u=1, i",
    "referer": "https://rmc.bytedance.com/",
    "sec-ch-ua": "\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "cross-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
}

    def get_shop_detail(self, promotion_ids):
        """抖音商品详情"""
        cookies = {
            "sessionid": os.getenv("SESSIONID") or 'e926b3ade98e099c4abd4fa2ab1fceb7',  # 暂时写死
        }

        params = {
            "is_h5": "1",
            "is_native_h5": "1",
            "verifyFp": get_fp(),
            "origin_type": "pc_buyin_group",
            "msToken": "",
            "a_bogus": "",
        }
        data = {
            "ui_params": "{\"from_live\":false,\"from_video\":null,\"three_d_log_data\":null,\"follow_status\":null,\"which_account\":null,\"ad_log_extra\":null,\"from_group_id\":null,\"bolt_param\":null,\"transition_tracker_data\":null,\"selected_ids\":null,\"window_reposition\":null,\"is_short_screen\":null,\"full_mode\":true}",
            "use_new_price": "1",
            "is_h5": "1",
            "bff_type": "2",
            "is_in_app": "0",
            "promotion_ids": promotion_ids,
            "meta_param": "",
            "source_page": "",
            "request_additions": "",
            "isFromVideo": "false",
            "enable_timing": "true"
        }
        resp = requests.post("https://ecom5-normal-hl.ecombdapi.com/aweme/v2/shop/promotion/pack/h5/",
                             headers=self.headers,
                             cookies=cookies,
                             params=params,
                             data=data, timeout=5
                             )

        return resp.json()

    def get_tracks(self, distance, tip_y):
        """
        获取轨迹参数
        """
        tracks = list()
        y, v, t, current = 0, 0, 1, 0
        mid = distance * 3 / 4
        exceed = random.randint(40, 90)
        z = random.randint(30, 150)
        while current < (distance + exceed):
            if current < mid / 2:
                a = 2
            elif current < mid:
                a = 3
            else:
                a = -3
            a /= 2
            v0 = v
            s = v0 * t + 0.5 * a * (t * t)
            current += int(s)
            v = v0 + a * t
            y += random.randint(-3, 3)
            z = z + random.randint(5, 10)
            tracks.append([min(current, (distance + exceed)), y, z])
        while exceed > 0:
            exceed -= random.randint(0, 5)
            y += random.randint(-3, 3)
            z = z + random.randint(5, 9)
            tracks.append([min(current, (distance + exceed)), y, z])
        tr = []
        for i, x in enumerate(tracks):
            tr.append({
                'x': x[0],
                'y': tip_y,
                'relative_time': x[2]
            })
        return tr

    def pass_slide(self, detail, fp=None):
        if not fp:
            fp = get_fp()
        logger.info('X音滑块'.center(50, '-'))
        logger.info('fp: %s || detail: %s' % (fp, detail))

        params = {
            "aid": "7886",
            "lang": "zh",
            "repoId": "579047",
            "subtype": "slide",
            "detail": detail,
            "server_sdk_env": "\\{\"idc\":\"lf\",\"region\":\"CN\",\"server_type\":\"passport\"\\}",
            "mode": "slide",
            "fp": fp,
            "h5_check_version": "4.0.5",
            "os_name": "windows",
            "platform": "pc",
            "os_type": "2",
            "h5_sdk_version": H5_SDK_VERSION,
            "webdriver": "false",
            "tmp": get_current_timestamp13()
        }
        # 1.获取图片 参数等等
        response = requests.get("https://verify.zijieapi.com/captcha/get", params=params,headers=self.headers)
        js_data = response.json()
        # logger.info(f'js_data: {js_data}')
        code = js_data['code']
        if code != 200:
            raise Exception("获取滑块/数据响应错误！")

        id = js_data['data']['id']
        bk_url = js_data['data']['question']['url1']
        target_url = js_data['data']['question']['url2']
        tip_y = js_data['data']['question']['tip_y']

        bg_bytes = requests.get(bk_url).content
        target_bytes = requests.get(target_url).content

        # 图片处理方案一:  图片存储+还原
        bg_path, target_path = "./bg.jpg", "./target.jpg"
        save_file(bg_path, bg_bytes)
        save_file(target_path, target_bytes)
        # distance = get_my_distance(repair_bytes, target_path, bg_path)  # css 比例缩

        # 图片处理方案二:  字节流
        # repair_bytes = image_restore(bg_bytes)
        # distance = get_distance(target_bytes, repair_bytes)  # css 比例缩

        distance = get_distance(target_bytes, bg_bytes)  # css 比例缩
        logger.info(f'识别距离: {distance}')
        # 滑动轨迹
        tracks = get_tracks(distance, tip_y)

        encrypt_data = {'modified_img_width': 340, 'id': id, 'mode': 'slide',
                        'ej9xmy6':tracks,
                        'mmNCFqKt': [], 'xbM80h': {'H299xC': {'x': 62, 'y': 307, 'time': 1730911689619},
                                                   'HFLa': {'x': 62, 'y': 307, 'time': 1730911689619},
                                                   'NjKjLvO8': [{'x': 83, 'y': 194, 'time': 1730911599077},
                                                                {'x': 39, 'y': 195, 'time': 1730911599111},
                                                                {'x': 0, 'y': 142, 'time': 1730911602473},
                                                                {'x': 378, 'y': 228, 'time': 1730911687146},
                                                                {'x': 62, 'y': 307, 'time': 1730911689619},
                                                                {'x': 58, 'y': 301, 'time': 1730911689666},
                                                                {'x': 58, 'y': 299, 'time': 1730911689966},
                                                                {'x': 64, 'y': 299, 'time': 1730911690002},
                                                                {'x': 76, 'y': 299, 'time': 1730911690041},
                                                                {'x': 82, 'y': 300, 'time': 1730911690081},
                                                                {'x': 88, 'y': 301, 'time': 1730911690123},
                                                                {'x': 89, 'y': 301, 'time': 1730911690164}],
                                                   'Tl0eACfJZ': [],
                                                   'lg7Fkjdy': [{'x': 58, 'y': 299, 'time': 1730911689963, 't': 0},
                                                                {'x': 58, 'y': 299, 'time': 1730911689966, 't': 0},
                                                                {'x': 63, 'y': 299, 'time': 1730911689999, 't': 0},
                                                                {'x': 65, 'y': 299, 'time': 1730911690006, 't': 0},
                                                                {'x': 68, 'y': 299, 'time': 1730911690013, 't': 0},
                                                                {'x': 69, 'y': 299, 'time': 1730911690016, 't': 0},
                                                                {'x': 71, 'y': 299, 'time': 1730911690022, 't': 0},
                                                                {'x': 73, 'y': 299, 'time': 1730911690032, 't': 0},
                                                                {'x': 77, 'y': 299, 'time': 1730911690042, 't': 0},
                                                                {'x': 78, 'y': 300, 'time': 1730911690053, 't': 0},
                                                                {'x': 80, 'y': 300, 'time': 1730911690066, 't': 0},
                                                                {'x': 82, 'y': 300, 'time': 1730911690081, 't': 0},
                                                                {'x': 84, 'y': 301, 'time': 1730911690097, 't': 0},
                                                                {'x': 86, 'y': 301, 'time': 1730911690105, 't': 0},
                                                                {'x': 88, 'y': 301, 'time': 1730911690123, 't': 0}],
                                                   'aSm0x': []},
                        'env': {'canvas_hash': '6cc1c0d0a0548ec6199de8c968aaedf1',
                                'webgl_hash': '62e11136104bd996d8ada7b925e51d65',
                                'font_hash': '6582817980d9ef25b65ffc78aeb4a279f7702604',
                                'audio_hash': 124.04347527516074, 'time_offset': -480, 'time_zone': 'Asia/Shanghai',
                                'languages': ['zh-CN'],
                                'plugins': ['PDF Viewer', 'Chrome PDF Viewer', 'Chromium PDF Viewer',
                                            'Microsoft Edge PDF Viewer', 'WebKit built-in PDF'], 'platform': 'Win32',
                                'max_touch_points': 10, 'webdriver': False, 'touch_actions': [],
                                'mouse_actions': ['1,1', '1,1'], 'device': {},
                                'os': {'name': 'Windows', 'version': '10'},
                                'browser': {'name': 'Chrome', 'version': '127.0.0.0'},
                                'engine': {'name': 'Blink', 'version': '127.0.0.0'},
                                'gpu': {'vendor': 'Google Inc. (Intel)',
                                        'renderer': 'ANGLE (Intel, Intel(R) UHD Graphics 630 (0x00003E9B) Direct3D11 vs_5_0 ps_5_0, D3D11)'},
                                'fps': 45, 'resolution': '1920,1080', 'browser_size': '1585,809',
                                'page_size': '1569,680', 'captcha_origin': '0,0', 'captcha_size': '380, 384',
                                'mask_time': 173091158866463, 'loading_time': 1730911589345,
                                'ready_time': 1730911589719}, 'a': 54}
        # 核心加密逻辑
        captchaBody = captcha_encrypt(encrypt_data)
        params = {
            "aid": "7886",
            "lang": "zh",
            "repoId": "579047",
            "subtype": "slide",
            "detail": detail,
            "server_sdk_env": "{\"idc\":\"lf\",\"region\":\"CN\",\"server_type\":\"passport\"}",
            "mode": "slide",
            "fp": fp,
            "h5_check_version": "4.0.5",
            "os_name": "windows",
            "platform": "pc",
            "os_type": "2",
            "h5_sdk_version": "3.5.64",
            "webdriver": "false",
            "tmp": get_current_timestamp13(),
            "xx-tt-dd": "qJI7ttpVdGKKbSBvYqmaf0aPo",
            # "msToken": "bOVJBfxJSrkSW13cGe-iKVBxW3u1Jk52fuw5FN8waTP9d1Sg8Qa9iPLHdBWyd5mItDwK1bu3kh6U8FVyft63P6Ry_xvcdJ2I-I3QEfG5MQ5ykGQi0VLfDplniOH63RRC-NXtMreOQ_xxB0uyNA4a4fiII0tOvAX4HNh6YCvqfFtleavXAEwP",
        }
        # time.sleep(random.uniform(2, 3))

        # 2.滑块校验
        response = requests.post("https://verify.zijieapi.com/captcha/verify",
                                params=params,
                                 headers=self.headers,
                                data=dumps({
                                    "captchaBody": captchaBody
                                })
                                )
        data_dict = response.json()
        logger.info(f'接口数据: {data_dict}')
        return {
            'fp': fp,
            'detail': detail,
            'message': data_dict['message'],
            'dy_api_data': data_dict
        }


@app.get("/")
async def read_root():
    return {"message": "Hello, World!"}


@app.get("/verify")
async def read_item(fp: str = '', detail: str = ''):
    dy_helper = DouYinSlide()
    data_dict = dy_helper.pass_slide(detail, fp)
    return data_dict


# detail = "Xvlzdf5kIHS4U9emlO1M4eelAyGSkrXF-jHWOzq277pNtRbc3lA0idznQkuFSt*OkBJgu1D0eGA7eR4IxEMx8hDBCb7sNTkwex5v1WzVRtplEuYzewLycmddW9prWaxwHe26iOAagtECMI4yGyAdjIeiQfCd48t-sRdKc230q9u1N38rOgXGLih7mbM5omxWQGaSRBiLp74CbTMV28*HeGGcvoLASv8snBgJbokJPyO2IvK2PFwHvlz46hQUMnMAXHAhW0I52m-Zl7PCerEZ*9jZVB97eA4qjEYJVjUvOCrAbOCEUEQFnH9YeKLHyLBnd51bosc83rPwLHiShvkHA8BkOKvsEokp4a-k9a*bhRTE2ajyNHlruiuOWz0DfGWdzPfOVSQ6T22dPH5F"
# fp = 'verify_m360zz18_M7E4x7Qk_4w5v_4omg_B8CR_1jpdpl9xHwKr'

'''
uvicorn main:app --reload

http://127.0.0.1:8000/verify?fp=verify_m360zz18_M7E4x7Qk_4w5v_4omg_B8CR_1jpdpl9xHwKr&detail=Xvlzdf5kIHS4U9emlO1M4eelAyGSkrXF-jHWOzq277pNtRbc3lA0idznQkuFSt*OkBJgu1D0eGA7eR4IxEMx8hDBCb7sNTkwex5v1WzVRtplEuYzewLycmddW9prWaxwHe26iOAagtECMI4yGyAdjIeiQfCd48t-sRdKc230q9u1N38rOgXGLih7mbM5omxWQGaSRBiLp74CbTMV28*HeGGcvoLASv8snBgJbokJPyO2IvK2PFwHvlz46hQUMnMAXHAhW0I52m-Zl7PCerEZ*9jZVB97eA4qjEYJVjUvOCrAbOCEUEQFnH9YeKLHyLBnd51bosc83rPwLHiShvkHA8BkOKvsEokp4a-k9a*bhRTE2ajyNHlruiuOWz0DfGWdzPfOVSQ6T22dPH5F
uvicorn main:app --reload --host 0.0.0.0 --port 9999

http://127.0.0.1:8000/verify?fp=verify_m3642dlj_cPJBfSJG_CSY3_4evn_88rM_zZ4valF74Rys
http://127.0.0.1:8000/verify?fp=verify_m3642dlj_cPJBfSJG_CSY3_4evn_88rM_zZ4valF74Rys&detail=aanSV08*cxFkxUDWVEzEVyUVsQT6gh0lnQ9r-iHJ*qiEsqSFEF-K2bVqJoLwuypvtcrYiYW2wrYhPLRuTE3HxQAGdNTjhM5RmPQbK2aRg0P1f621iH*ikdsbQriODqdcGyga3GccEoDsHMb1qIzFoHWqZgWNOiHZZ-gasppHuvP74OSEnFVtGVtMxcL0QkadOB3hmkYKcqLqEnsLKzEbGR0zp63Mbj-OmZOj8JVWzYfXYhO9eUmfE3FuZtwPMRqRv-22-Me*K7VtQaPkhnU-tE08fDhuSpqpe-NhzZ5XgTaluF2wPG2umH1Vco8DO1i053dps5yMSgxXdE4uNnMkE*Ij7DmJ*hv7PdeszouD5wfbdf9oWGkWmunU64vt3u8nVoPId2wxkLiqWIKz


'''
